package puzzle.slilin2.solver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import puzzle.common.data.HatanException;
import puzzle.common.point.Pnt;
import puzzle.common.solver.AbstractSolver;
import puzzle.common.solver.BoardSnapshot;
import puzzle.common.solver.SolutionState;
import puzzle.common.solver.Track;
import puzzle.common.solver.TrackTopology;
import puzzle.common.solver.topology.TrackAnalyzer;
import puzzle.slilin2.data.NumberCellState;
import puzzle.slilin2.data.SlilinBoard;
import puzzle.slilin2.data.SlilinPntHelper;
import tomoto.util.CollectionUtil;

public class SlilinSolver extends AbstractSolver<SlilinBoard, TrackTopology, SlilinAssumption> {

	public SlilinSolver() {
		addRule(new BasicNumberRule());
		addRule(new DotRule());
		addRule(new NumberCornerRule());
		addRule(new IncommingCornerRule());

		addInitialRule(new ThreePairInitialRule());

		addTopologicalRule(new AvoidLoopTopologicalRule());
		addTopologicalRule(new ClosureTopologicalRule());
	}

	@Override
	protected SolutionState checkSolutionState(SlilinBoard b, TrackTopology topology) {
		List<Track> tracks = topology.getTracks();
		boolean isComplete = true;
		for (int y = 0; y < b.getLogicalHeight(); y++) {
			for (int x = 0; x < b.getLogicalWidth(); x++) {
				Pnt p = SlilinPntHelper.getPhysicalPoint(new Pnt(x, y));
				int n = b.getNumber(p);
				if (n != NumberCellState.EMPTY) {
					Pnt[] ps = SlilinPntHelper.getLinePointsAroundNumber(b, p);
					LineCount lcs = new LineCount(b, ps);
					if (lcs.getExistsCount() > n || lcs.getForbiddenCount() > 4 - n) {
						return SolutionState.HATAN;
					} else if (lcs.getExistsCount() < n) {
						isComplete = false;
					}
				}
			}
		}

		for (int y = 1; y < b.height - 1; y += 2) {
			for (int x = 1; x < b.width - 1; x += 2) {
				Pnt p = new Pnt(x, y);
				Pnt[] ps = SlilinPntHelper.getLinePointsAroundDot(b, p);
				LineCount lcs = new LineCount(b, ps);
				if (lcs.getExistsCount() > 2
						|| (lcs.getExistsCount() == 1 && lcs.getForbiddenCount() == 3)) {
					return SolutionState.HATAN;
				} else if (lcs.getExistsCount() == 1) {
					isComplete = false;
				}
			}
		}

		if (tracks.size() != 1) {
			for (Track track : tracks) {
				if (!track.isOpen()) {
					return SolutionState.HATAN;
				}
			}
			return SolutionState.INCOMPLETE;
		} else {
			return isComplete ? SolutionState.COMPLETE : SolutionState.INCOMPLETE;
		}

	}

	@Override
	protected TrackTopology analyzeTopology(SlilinBoard b) {
		SlilinTrackAnalyzable g = new SlilinTrackAnalyzable(b);
		TrackAnalyzer z = new TrackAnalyzer();
		return z.analyze(g);
	}

	@Override
	protected List<SlilinAssumption> assume(SlilinBoard b0, TrackTopology topology,
			SlilinAssumption prevAssumption) {
		List<Track> tracks = topology.getTracks();
		List<Pnt> ends = new ArrayList<Pnt>();
		for (Track track : tracks) {
			ends.add(track.getEnds().car);
			ends.add(track.getEnds().cdr);
		}

		List<Pnt> assumedPoints = new ArrayList<Pnt>();
		for (Pnt p0 : ends) {
			for (Pnt p : SlilinPntHelper.getLinePointsAroundDot(b0, p0)) {
				CollectionUtil.addOnce(assumedPoints, p);
			}
		}

		List<SlilinAssumption> assumptions = new ArrayList<SlilinAssumption>();
		for (Pnt p : assumedPoints) {
			if (!b0.getLineCellState(p).isFixed()) {
				assumptions.add(new SlilinAssumption(p));
			}
		}

		return assumptions;
	}

	@Override
	protected void applyAssumption(
			List<BoardSnapshot<SlilinBoard, TrackTopology, SlilinAssumption>> bss, SlilinBoard b,
			SlilinAssumption a) throws HatanException {
		b.getLineCellState(a.p).setExists(true);
		b.getLineCellState(a.p).setAssumed(true);
	}

	@Override
	protected void excludeAssumption(SlilinBoard b0, SlilinAssumption a) throws HatanException {
		b0.getLineCellState(a.p).setForbidden(true);
	}

	@Override
	protected boolean isAlreadyTried(
			List<BoardSnapshot<SlilinBoard, TrackTopology, SlilinAssumption>> bss, SlilinBoard b0,
			SlilinAssumption a) {
		if (b0.getLineCellState(a.p).isFixed()) {
			return true;
		}

		for (BoardSnapshot<SlilinBoard, TrackTopology, SlilinAssumption> bs : bss) {
			if (bs.getBoard().getLineCellState(a.p).exists()) {
				return true;
			}
		}

		return false;
	}

	@Override
	protected void sortBoardSnapshots(
			List<BoardSnapshot<SlilinBoard, TrackTopology, SlilinAssumption>> bss) {
		Collections.sort(bss, TrackTopology.TOTAL_LEG_BOARD_COMPARATOR);
	}

}
